package edu.cqie.common.util;

import edu.cqie.common.exception.BusinessException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.UUID;

import static io.jsonwebtoken.Claims.SUBJECT;

/**
 * 这里是生成token的工具类
 */
@Slf4j
@Component
public class JwtUtil {


    @Getter
    private static String adminSecretKey;

    @Getter
    private static Long adminTtl;


    @Value("${agrisrm.jwt.tokenSecretKey}")
    public void setAdminSecretKey(String key) {
        adminSecretKey = key;
    }

    @Value("${agrisrm.jwt.tokenTTLSec}")
    public void setAdminTtl(long ttl) {
        adminTtl = ttl;
    }


    /**
     * 加密算法
     */
    private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;

    /*
    这些是一组预定义的声明，它们 不是强制性的，而是推荐的 ，以 提供一组有用的、可互操作的声明 。
    iss: jwt签发者
    sub: jwt所面向的用户
    aud: 接收jwt的一方
    exp: jwt的过期时间，这个过期时间必须要大于签发时间
    nbf: 定义在什么时间之前，该jwt都是不可用的.
    iat: jwt的签发时间
    jti: jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击
     */
    //TODO: 需要将权限写入JWT当中
    public static String createJWT(Long userId) {
        // 令牌id
        String uuid = UUID.randomUUID().toString();

        return Jwts.builder()
                // 设置头部信息header
                .header()
                .add("typ", "JWT")
                .add("alg", "HS256")
                .and()
                // 设置自定义负载信息payload
                .claim("userId", userId)
                // 令牌ID
                .id(uuid)
                // 过期日期
                .expiration(new Date(System.currentTimeMillis() + 1000 * adminTtl))
                // 签发时间
                .issuedAt(new Date(System.currentTimeMillis()))
                // 主题
                .subject(SUBJECT)
                // 签发者
                .issuer("AgriSRM")
                // 签名
                .signWith(Keys.hmacShaKeyFor(adminSecretKey.getBytes()), ALGORITHM)
                .compact();
    }

    /**
     * 解析token
     * @param token token
     * @return Jws<Claims>
     */
    public static Jws<Claims> parseClaim(String token) throws JwtException {

        Jws<Claims> claimsJws = Jwts.parser()
                .verifyWith(Keys.hmacShaKeyFor(adminSecretKey.getBytes()))
                .build()
                .parseSignedClaims(token);


        Claims claims = claimsJws.getPayload();


        long expiration = claims.getExpiration().getTime();

        if (System.currentTimeMillis() > expiration) {
            throw new ExpiredJwtException(claimsJws.getHeader(), claims, "JWT Expired");
        }

        return claimsJws;
    }

    public static JwsHeader parseHeader(String token) {
        return parseClaim(token).getHeader();
    }

    public static Claims parsePayload(String token) throws JwtException {
        return parseClaim(token).getPayload();
    }

    @Data
    public static class JwtBo {

        private Long userId;

        private Long expiration;
    }

    public static JwtBo getUserInfo(String token){
        JwtBo jwtBo = new JwtBo();

        Claims claims = parsePayload(token);

        jwtBo.setUserId(claims.get("userId", Long.class));
        jwtBo.setExpiration(claims.getExpiration().getTime());

        return jwtBo;
    }

    /**
     * 解析token  返回Claims信息
     */
    public static Long getUserId(String token) {

        if (token == null) {
            throw new BusinessException("Error Token", HttpStatus.UNAUTHORIZED);
        }
        //解析token，并获取其声明（Claims）。
        Claims claims = JwtUtil.parsePayload(token);


        //从Claims中获取用户ID
        return claims.get("userId", Long.class);
    }

    public static Long getExpireTime(String token) {
        if (token == null) {
            throw new BusinessException("Error Token", HttpStatus.UNAUTHORIZED);
        }
        Claims claims = JwtUtil.parsePayload(token);
        return claims.getExpiration().getTime();
    }


    public JwtUtil() {
    }

    public JwtUtil(String adminSecretKey, Long adminTTl) {
        this.setAdminTtl(adminTTl);
        this.setAdminSecretKey(adminSecretKey);
    }


}
